package org.wzy.spring.framework.aop;

import org.wzy.spring.framework.transaction.config.AopAutoProxyConfigurer;
import org.wzy.spring.framework.util.ClassUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Spring Aop JDK动态代理实现
 *
 * @author wzy_h
 * @version v_1.0
 * @date 2018/5/26 15:22
 * @since 1.8
 */
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {

    /**
     * 用于保存目标增强方法及增强类、自定义通知逻辑的关系
     */
    private AopAutoProxyConfigurer aopAutoProxyConfigurer = new AopAutoProxyConfigurer();

    /**
     * 被代理的对象, 作为引用被保存下来
     */
    private Object target;

    @Override
    public Object getProxy() {
        return getProxy(ClassUtils.getDefaultClassLoader());
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        if (null != target) {
            Class<?> targetClass = target.getClass();
            return Proxy.newProxyInstance(classLoader, targetClass.getInterfaces(), this);
        }
        return null;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 这里的参数 method 为代理后的, 但是 aopAutoProxyConfigurer 中存储的却是原始方法, 因此需要先作一次转换
        Method originalMethod = this.target.getClass().getMethod(method.getName(), method.getParameterTypes());
        boolean contains = aopAutoProxyConfigurer.contains(originalMethod);
        if (contains) {
            // 调用目标方法之前, 先执行增强的代码
            AopAutoProxyConfigurer.Aspect aspect = aopAutoProxyConfigurer.get(originalMethod);
            aspect.getPoints()[0].invoke(aspect.getAspect());
        }

        Object result = method.invoke(this.target, args);

        if (contains) {
            // 调用目标方法之后, 先执行增强的代码
            AopAutoProxyConfigurer.Aspect aspect = aopAutoProxyConfigurer.get(originalMethod);
            aspect.getPoints()[1].invoke(aspect.getAspect());
        }
        // 将最原始的返回值返回
        return result;
    }

    public void setAopAutoProxyConfigurer(AopAutoProxyConfigurer aopAutoProxyConfigurer) {
        this.aopAutoProxyConfigurer = aopAutoProxyConfigurer;
    }

    public void setTarget(Object target) {
        this.target = target;
    }
}
